use std::{collections::BTreeMap, ops::Deref};

use darling::{FromDeriveInput, FromMeta};
use proc_macro::TokenStream;
use quote::{format_ident, quote};
use syn::{parse_macro_input, DeriveInput, Ident};
use proc_macro2::TokenStream as T2;

use crate::periph2::rcc::{self, RccAxB};

#[derive(Debug, FromDeriveInput)]
#[darling(attributes(gpio))]
pub struct GpioConfig {
    #[darling(multiple)]
    pub io: Vec<GpioAttr>,
    pub init: String,
}

impl GpioConfig {

    fn build_exti(&self) -> T2 {
        let mut mr = Vec::new();
        let mut rtsr = Vec::new();
        let mut ftsr = Vec::new();

        let mut exticr1 = Vec::new();
        let mut exticr2 = Vec::new();
        let mut exticr3 = Vec::new();
        let mut exticr4 = Vec::new();
        
        for attr in self.io.iter() {
            if attr.exti_down || attr.exti_up {
                let pin = attr.pin();
                mr.push(format_ident!("mr{}", pin));
                if attr.exti_up {
                    rtsr.push(format_ident!("tr{}", pin));
                }
                if attr.exti_down {
                    ftsr.push(format_ident!("tr{}", pin));
                }
                let exti_val = attr.exti();
                let exti = format_ident!("exti{}", pin);
                if pin <= 3 {
                    exticr1.push(quote! {
                        #exti().variant(#exti_val)
                    });
                } else if pin <= 7 {
                    exticr2.push(quote! {
                        #exti().variant(#exti_val)
                    });
                } else if pin <= 11 {
                    exticr3.push(quote! {
                        #exti().variant(#exti_val)
                    });
                } else {
                    exticr4.push(quote! {
                        #exti().variant(#exti_val)
                    });
                }
            }
        }
        let mut expand = quote! {};

        if !mr.is_empty() {
            expand.extend(quote! {
                let exti: stm32f1::stm32f103::EXTI = unsafe { ::core::mem::transmute(()) };
                let afio: stm32f1::stm32f103::AFIO = unsafe { ::core::mem::transmute(()) };
            });
            expand.extend(quote! {
                exti.imr.modify(|_, w| {
                    w #(.#mr().unmasked())*
                });
            });
            if !exticr1.is_empty() {
                expand.extend(quote! {
                    afio.exticr1.modify(|_, w| w #(.#exticr1)*);
                });
            }
            if !exticr2.is_empty() {
                expand.extend(quote! {
                    afio.exticr2.modify(|_, w| w #(.#exticr2)*);
                });
            }
            if !exticr3.is_empty() {
                expand.extend(quote! {
                    afio.exticr3.modify(|_, w| w #(.#exticr3)*);
                });
            }
            if !exticr4.is_empty() {
                expand.extend(quote! {
                    afio.exticr4.modify(|_, w| w #(.#exticr4)*);
                });
            }

            if !rtsr.is_empty() {
                expand.extend(quote! {
                    exti.rtsr.modify(|_, w| {
                        w #(.#rtsr().enabled())*
                    });
                });
            }

            if !ftsr.is_empty() {
                expand.extend(quote! {
                    exti.ftsr.modify(|_, w| {
                        w #(.#ftsr().enabled())*
                    });
                });
            }
        }

        expand
    }

    pub fn build(&self) -> T2 {
        let mut map: BTreeMap<String, Vec<&GpioAttr>> = BTreeMap::new();
        let mut afio_flag = false;
        for attr in self.io.iter() {
            let gpio = attr.gpio_id();
            if attr.exti_down || attr.exti_up {
                afio_flag = true
            }
            let ret = map.get_mut(&gpio);
            if let Some(vec) = ret {
                vec.push(attr);
            } else {
                let mut vec = Vec::new();
                vec.push(attr);
                map.insert(gpio, vec);
            }
        }
        let mut group_expand = quote! {};
        for (key, value) in map.iter() {
            let r = self.build_group(&key, &value);
            group_expand.extend(r);
        }
        let exti = self.build_exti();
        group_expand.extend(exti);

        let init_fn = format_ident!("{}", self.init);
        let init_fn_no_rcc = format_ident!("{}_no_rcc", self.init);

        let mut fields: Vec<Ident> = map.keys().map(|id| format_ident!("iop{}en", id)).collect();
        if afio_flag {
            fields.push(format_ident!("afioen"));
        }
        let rcc_init = rcc::rcc_enable_all(RccAxB::APB2, &fields, false);

        let mut utils = quote! {};
        for io in self.io.iter() {
            if io.set {
                utils.extend(io.build_set());
            }
            if io.reset {
                utils.extend(io.build_reset());
            }
            if io.read {
                utils.extend(io.build_read());
            }
            if io.toggle {
                utils.extend(io.build_toggle());
            }
            if io.exti_down || io.exti_up {
                utils.extend(io.build_is_exti_pr());
                utils.extend(io.build_clear_exti_pr());
            }
        }

        quote! {

            pub fn #init_fn_no_rcc() {
                #group_expand
            }

            pub fn #init_fn() {
                #rcc_init
                #group_expand
            }
            #utils
        }
    }

    fn build_group(&self, gpio_id: &str, attrs: &[&GpioAttr]) -> T2 {
        let mut crl = Vec::new();
        let mut crh = Vec::new();
        let mut odr = Vec::new();

        for attr in attrs {
            if attr.pin() < 8 {
                crl.push( attr.build_cr() );
            } else {
                crh.push( attr.build_cr() );
            }
            if attr.is_input_down_up_or_out() {
                odr.push( attr.build_odr() );
            }
        }
        let gpio = format_ident!("gpio{}", gpio_id);
        let gpio_u = format_ident!("GPIO{}", gpio_id.to_uppercase());
        let mut expand = quote! {};
        if !crl.is_empty() {
            expand.extend(quote! {
                #gpio.crl.modify(|_, w| w #(.#crl)*);
            });
        }
        if !crh.is_empty() {
            expand.extend(quote! {
                #gpio.crh.modify(|_, w| w #(.#crh)*);
            });
        }
        if !odr.is_empty() {
            expand.extend(quote! {
                #gpio.bsrr.write(|w| w #(.#odr)*);
            });
        }
        quote! {
            let #gpio: stm32f1::stm32f103::#gpio_u = unsafe { ::core::mem::transmute(()) };
            #expand
        }
    }
}

#[derive(Debug, FromMeta)]
pub struct GpioAttr {
    pub name: String,
    pub io: String,
    pub mode: GpioMode,

    #[darling(default)]
    pub level: bool,

    #[darling(default)]
    pub set: bool,
    #[darling(default)]
    pub reset: bool,
    #[darling(default)]
    pub toggle: bool,
    #[darling(default)]
    pub read: bool,
    #[darling(default)]
    pub exti_up: bool,
    #[darling(default)]
    pub exti_down: bool,
}

impl GpioAttr { 

    fn exti(&self) -> u8 {
        match self.gpio_id().deref() {
            "a" => 0,
            "b" => 1,
            "c" => 2,
            "d" => 3,
            "e" => 4,
            "f" => 5,
            "g" => 6,
            _ => panic!("invalid gpio")
        }
    }

    fn gpio_id(&self) -> String {
        let n: &str = self.io.deref();
        let name = &n[1..2];
        name.to_lowercase()
    }

    fn pin(&self) -> u8 {
        let n = self.io.deref();
        (&n[2..]).parse().unwrap()
    }

    fn build_is_exti_pr(&self) -> T2 {
        let pr = format_ident!("pr{}", self.pin());
        let is_exti_pr_fn = format_ident!("{}_is_exti_pr", self.name);
        quote! {
            pub fn #is_exti_pr_fn() -> bool {
                let exti: stm32f1::stm32f103::EXTI = unsafe { ::core::mem::transmute(()) };
                exti.pr.read().#pr().is_pending()
            }
        }
    }

    fn build_clear_exti_pr(&self) -> T2 {
        let pr = format_ident!("pr{}", self.pin());
        let clear_exti_pr_fn = format_ident!("{}_clear_exti_pr", self.name);
        quote! {
            pub fn #clear_exti_pr_fn() {
                let exti: stm32f1::stm32f103::EXTI = unsafe { ::core::mem::transmute(()) };
                exti.pr.write(|w| w.#pr().clear());
            }
        }
    }

    fn build_cr(&self) -> T2 {
        let pin = self.pin();
        let mode = format_ident!("mode{}", pin);
        let cnf = format_ident!("cnf{}", pin);
        match self.mode {
            GpioMode::AIN => quote! { #mode().input().#cnf().push_pull() },
            GpioMode::InputFloat => quote! { #mode().input().#cnf().open_drain() },
            GpioMode::InputUp | GpioMode::InputDown => quote! { #mode().input().#cnf().alt_push_pull() },
            GpioMode::OutputPP => quote! { #mode().output50().#cnf().push_pull() },
            GpioMode::OutputOD => quote! { #mode().output50().#cnf().open_drain() },
            GpioMode::AfPP => quote! { #mode().output50().#cnf().alt_push_pull() },
            GpioMode::AfOD => quote! { #mode().output50().#cnf().alt_open_drain() }
        }
    }

    fn is_input_down_up_or_out(&self) -> bool {
        match self.mode {
            GpioMode::InputDown | GpioMode::InputUp | GpioMode::OutputOD | GpioMode::OutputPP=> true,
            _ => false,
        }
    }

    fn build_odr(&self) -> T2 {
        let pin = self.pin();
        let bs = format_ident!("bs{}", pin);
        let br = format_ident!("br{}", pin);
        match self.mode {
            GpioMode::InputDown  => quote! { #br().reset() },
            GpioMode::InputUp => quote! { #bs().set() },
            GpioMode::OutputOD | GpioMode::OutputPP => {
                if self.level {
                    quote! { #bs().set() }
                } else {
                    quote! { #br().reset() }
                }
            }
            _ => quote! {}
        }
    }

    fn build_read(&self) -> T2 {
        let pin = self.pin();
        let idr = format_ident!("idr{}", pin);
        let id = self.gpio_id();
        let gpio = format_ident!("GPIO{}", id.to_uppercase());
        let read_fn = format_ident!("{}_read", self.name);
        quote! {
            #[inline(always)]
            pub fn #read_fn() -> bool {
                let gpio: stm32f1::stm32f103::#gpio = unsafe { ::core::mem::transmute(()) };
                gpio.idr.read().#idr().bit()
            }
        }
    }

    fn build_set(&self) -> T2 {
        let pin = self.pin();
        let bs = format_ident!("bs{}", pin);
        let id = self.gpio_id();
        let gpio = format_ident!("GPIO{}", id.to_uppercase());
        let set_fn = format_ident!("{}_set", self.name);
        quote! {
            #[inline(always)]
            pub fn #set_fn() {
                let gpio: stm32f1::stm32f103::#gpio = unsafe { ::core::mem::transmute(()) };
                gpio.bsrr.write(|w| w.#bs().set());
            }
        }
    }

    fn build_reset(&self) -> T2 {
        let pin = self.pin();
        let br = format_ident!("br{}", pin);
        let id = self.gpio_id();
        let gpio = format_ident!("GPIO{}", id.to_uppercase());
        let reset_fn = format_ident!("{}_reset", self.name);
        quote! {
            #[inline(always)]
            pub fn #reset_fn() {
                let gpio: stm32f1::stm32f103::#gpio = unsafe { ::core::mem::transmute(()) };
                gpio.bsrr.write(|w| w.#br().reset());
            }
        }
    }

    fn build_toggle(&self) -> T2 {
        let pin = self.pin();
        let odr = format_ident!("odr{}", pin);
        let id = self.gpio_id();
        let gpio = format_ident!("GPIO{}", id.to_uppercase());
        let toggle_fn = format_ident!("{}_toggle", self.name);
        quote! {
            #[inline(always)]
            pub fn #toggle_fn() {
                let gpio: stm32f1::stm32f103::#gpio = unsafe { ::core::mem::transmute(()) };
                gpio.odr.modify(|r, w| w.#odr().bit(!r.#odr().bit()))
            }
        }
    }
}

#[derive(Debug, FromMeta)]
pub enum GpioMode {
    #[darling(rename = "AIN")]
    AIN,

    #[darling(rename = "IN_FLOAT")]
    InputFloat,

    #[darling(rename = "IN_UP")]
    InputUp,

    #[darling(rename = "IN_DOWN")]
    InputDown,

    #[darling(rename = "OUT_PP")]
    OutputPP,

    #[darling(rename = "OUT_OD")]
    OutputOD,

    #[darling(rename = "AF_PP")]
    AfPP,

    #[darling(rename = "AF_OD")]
    AfOD,
}

pub fn build(input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as DeriveInput);
    let name = &input.ident;
    let gpio_config = GpioConfig::from_derive_input(&input).unwrap();
    let build = gpio_config.build();
    let expand = quote! {
        impl #name {
            #build
        }
    };
    expand.into()
}



